/* 
 * Shuriken is the legal property of its developers. Please refer to the
 * COPYRIGHT file distributed with this
 * source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

using System;
using System.Collections.Generic;
using System.Text;
using Shuriken.Core.Store;

namespace Shuriken.Core.Ranking
{
    /// <summary>
    /// Implements the ranking algorithm used in Quick Silver. 
    /// </summary>
    /// <remarks>
    /// <seealso cref="http://docs.blacktree.com/quicksilver/development/string_ranking"/>
    /// Original pseudo code 
    ///- (float) scoreForAbbreviation:(NSString *)abbreviation inRange:(NSRange)searchRange fromRange:(NSRange)abbreviationRange hitMask:(NSMutableIndexSet *)mask{
    ///    float score,remainingScore;
    ///    int i,j;
    ///    NSRange matchedRange,remainingSearchRange;
    ///    if (!abbreviationRange.length) return 0.9; //deduct some points for all remaining letters
    ///    if (abbreviationRange.length>searchRange.length)return 0.0;
    ///    /*
    ///     {
    ///         matchedRange=[[self | rangeOfString:[abbreviation substringWithRange:NSMakeRange(abbreviationRange.location,1) ]]
    ///                                  options:NSCaseInsensitiveSearch
    ///                                    range:searchRange];
    ///
    ///         if (matchedRange.location=NSNotFound) return 0.9;
    ///         searchRange.length-=matchedRange.location-searchRange.location;
    ///         searchRange.location=matchedRange.location;
    ///     }
    ///     */
    ///    for (i=abbreviationRange.length; i>0;i--){ //Search for steadily smaller portions of the abbreviation
    ///        matchedRange=[self rangeOfString:[abbreviation substringWithRange:NSMakeRange(abbreviationRange.location,i)] options:NSCaseInsensitiveSearch range:searchRange];
    ///
    ///        if (NSNotFound == matchedRange.location) continue;
    ///        if (matchedRange.location+abbreviationRange.length>NSMaxRange(searchRange)) continue;
    ///
    ///        if (mask) [mask addIndexesInRange:matchedRange];
    ///
    ///        remainingSearchRange.location=NSMaxRange(matchedRange);
    ///        remainingSearchRange.length=NSMaxRange(searchRange)-remainingSearchRange.location;
    ///
    ///        // Search what is left of the string with the rest of the abbreviation
    ///        remainingScore = [self scoreForAbbreviation:abbreviation inRange:remainingSearchRange fromRange:NSMakeRange(abbreviationRange.location+i,abbreviationRange.length-i) hitMask:mask];
    ///
    ///        if (remainingScore) {
    ///            score = remainingSearchRange.location - searchRange.location;
    ///            // ignore skipped characters if is first letter of a word
    ///            if (matchedRange.location>searchRange.location){//if some letters were skipped
    ///                j=0;
    ///                if ([[NSCharacterSet whitespaceCharacterSet] characterIsMember: [self characterAtIndex: matchedRange.location-1]]) {
    ///                    for (j=matchedRange.location-2; j >= (int)searchRange.location; j--) {
    ///                        if ([[NSCharacterSet whitespaceCharacterSet] characterIsMember: [self characterAtIndex:j]]) score--;
    ///                        else score-=0.15;
    ///                    }
    ///
    ///                } else if ([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember: [self characterAtIndex: matchedRange.location]]) {
    ///                    for (j=matchedRange.location-1; j >= (int)searchRange.location; j--) {
    ///                        if ([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember: [self characterAtIndex:j]])
    ///                            score--;
    ///                        else
    ///                            score-=0.15;
    ///                    }
    ///                } else {
    ///                    score -= matchedRange.location - searchRange.location;
    ///                }
    ///
    ///            }
    ///
    ///            score += remainingScore * remainingSearchRange.length;
    ///            score /= searchRange.length;
    ///            return score;
    ///        }
    ///    }
    ///    return 0;
    ///}   
    /// </remarks>
    public class QSRankProvider : IRankProvider
    {
        #region IRankProvider Members

        /// <summary>
        /// Calculates score for the give abbreviation
        /// </summary>
        /// <param name="candidate">candidate</param>
        /// <param name="abbreviation">abbreviation</param>
        /// <returns>score</returns>
        private double ScoreForAbbreviation(string candidate, string abbreviation)
        {
            return ScoreForAbbreviation(candidate, abbreviation, null);
        }

        /// <summary>
        /// Calculates score for the give abbreviation
        /// </summary>
        /// <param name="candidate">candidate</param>
        /// <param name="abbreviation">abbreviation</param>
        /// <param name="mask">hit mask</param>
        /// <returns>score</returns>
        private double ScoreForAbbreviation(string candidate, string abbreviation, MutableIndexSet mask)
        {
            return ScoreForAbbreviation(candidate, abbreviation, Range.MakeRange(0, candidate.Length), Range.MakeRange(0, abbreviation.Length), mask);
        }


        /// <summary>
        /// Calculates score for the give abbreviation
        /// </summary>
        /// <param name="candidate">candidate</param>
        /// <param name="abbreviation">abbreviation</param>
        /// <param name="searchRange">search range</param>
        /// <param name="abbreviationRange">abbreviation range</param>
        /// <param name="mask">hit mask</param>
        /// <returns>score</returns>
        private double ScoreForAbbreviation(string candidate, string abbreviation, Range searchRange, Range abbreviationRange, MutableIndexSet mask)
        {
            //Original FuncDef- (float) scoreForAbbreviation:(NSString *)abbreviation inRange:(NSRange)searchRange fromRange:(NSRange)abbreviationRange hitMask:(NSMutableIndexSet *)mask{
            // System.Diagnostics.Trace.WriteLine("ScoreForAbbreviation[" + candidate + ", " + abbreviation +"," +searchRange + "," + abbreviationRange + "]");
            // System.Diagnostics.Trace.WriteLine(":::[" + candidate.Substring(searchRange.Location) + " , " + abbreviation.Substring(abbreviationRange.Location) + "].");

            double score, remainingScore;
            int i, j;

            Range matchedRange = new Range(0, 0);
            Range remainingSearchRange = new Range(0, 0);

            if (abbreviationRange.Length == 0) return 0.9; //deduct some points for all remaining letters
            if (abbreviationRange.Length > searchRange.Length) return 0.0;

            for (i = abbreviationRange.Length; i > 0; i--)
            {
                //Search for steadily smaller portions of the abbreviation
                // matchedRange=[self rangeOfString:[abbreviation substringWithRange:NSMakeRange(abbreviationRange.location,i)] options:NSCaseInsensitiveSearch range:searchRange];
                matchedRange = RangeOfString(candidate, SubstringWithRange(abbreviation, Range.MakeRange(abbreviationRange.Location, i)), searchRange);
                if (Range.NotFound == matchedRange.Location) continue;
                if (matchedRange.Location + abbreviationRange.Length > Range.GetMaxRange(searchRange)) continue;

                if (mask != null) mask.addIndexesInRange(matchedRange);

                remainingSearchRange.Location = Range.GetMaxRange(matchedRange);
                remainingSearchRange.Length = Range.GetMaxRange(searchRange) - remainingSearchRange.Location;

                // Search what is left of the string with the rest of the abbreviation
                // remainingScore = [self scoreForAbbreviation:abbreviation inRange:remainingSearchRange fromRange:NSMakeRange(abbreviationRange.location+i,abbreviationRange.length-i) hitMask:mask];
                remainingScore = ScoreForAbbreviation(candidate, abbreviation, remainingSearchRange, Range.MakeRange(abbreviationRange.Location + i, abbreviationRange.Length - i), mask);

                if (remainingScore != 0)
                {
                    score = remainingSearchRange.Location - searchRange.Location;
                    // ignore skipped characters if is first letter of a word
                    if (matchedRange.Location > searchRange.Location)
                    {//if some letters were skipped
                        j = 0;

                        //if ([[NSCharacterSet whitespaceCharacterSet] characterIsMember: [self characterAtIndex: matchedRange.location-1]]) {
                        if (Char.IsWhiteSpace(candidate, matchedRange.Location - 1))
                        {
                            for (j = matchedRange.Location - 2; j >= (int)searchRange.Location; j--)
                            {
                                // if ([[NSCharacterSet whitespaceCharacterSet] characterIsMember: [self characterAtIndex:j]]) score--;
                                // else score-=0.15;
                                if (Char.IsWhiteSpace(candidate, j))
                                {
                                    score--;
                                }
                                else
                                {
                                    score -= 0.15;
                                }
                            }
                        }
                        //} else if ([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember: [self characterAtIndex: matchedRange.location]]) 
                        else if (Char.IsUpper(candidate, matchedRange.Location))
                        {
                            for (j = matchedRange.Location - 1; j >= (int)searchRange.Location; j--)
                            {
                                //if ([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember: [self characterAtIndex:j]])
                                if (Char.IsUpper(candidate, j))
                                    score--;
                                else
                                    score -= 0.15;
                            }
                        }
                        else
                        {
                            score -= matchedRange.Location - searchRange.Location;
                        }

                    }

                    score += remainingScore * remainingSearchRange.Length;
                    score /= searchRange.Length;
                    return score;

                }
            }

            return 0;
        }

        /// <summary>
        /// Calculates rank score for a given expression and candidate
        /// </summary>
        /// <param name="expression">search expression</param>
        /// <param name="candidate">candidate for this search</param>
        /// <param name="hitMask">hitmask for this search</param>
        /// <returns>score from 0 to 99, the higher the value is the better</returns>
        public double CalculateRank(ISearchable candidate, string expression, Shuriken.Core.Ranking.MutableIndexSet hitMask)
        {
            if (candidate == null) return 0; 
            return CalculateRank(candidate.Name, expression, hitMask);
        }

        /// <summary>
        /// Calculates rank score for a given expression and candidate
        /// </summary>
        /// <param name="expression">search expression</param>
        /// <param name="candidate">candidate for this search</param>
        /// <returns>score from 0 to 1, the higher the value is the better</returns>
        public double CalculateRank(string candidate, string expression)
        {
            return CalculateRank(candidate, expression, null);
        }


        /// <summary>
        /// Calculates rank score for a given expression and candidate
        /// </summary>
        /// <param name="expression">search expression</param>
        /// <param name="candidate">candidate for this search</param>
        /// <param name="hitMask"></param>
        /// <returns>score from 0 to 1, the higher the value is the better</returns>
        public double CalculateRank(string candidate, string expression, MutableIndexSet hitMask)
        {
            if (candidate == null || candidate == string.Empty) return 0;
            if (expression == null || expression == string.Empty) return 0;

            return ScoreForAbbreviation(candidate, expression, hitMask);
        }

        #endregion

        /// <summary>
        /// Returns the range of the first occurrence within the receiver of a given string.
        /// </summary>
        /// <param name="aString">The string to search on. This value must not be nil</param>
        /// <param name="aString">The string to search for. This value must not be nil</param>
        /// <returns>
        /// An NSRange structure giving the location and length in the receiver of the first occurrence of subString. Returns {NSNotFound, 0} if subString is not found, is the null string, or is empty.\
        /// </returns>
        public Range RangeOfString(string subject, string aString)
        {
            return RangeOfString(subject, aString, new Range(0, subject.Length));
        }


        /// <summary>
        /// Returns the range of the first occurrence within the receiver of a given string.
        /// </summary>
        /// <param name="subject">The string to search on. This value must not be nil</param>
        /// <param name="aString">The string to search for. This value must not be nil</param>
        /// <param name="searchRange">Range to search in</param>
        /// <returns>
        /// An NSRange structure giving the location and length in the receiver of the first occurrence of subString. Returns {NSNotFound, 0} if subString is not found, is the null string, or is empty.\
        /// </returns>
        public Range RangeOfString(string subject, string aString, Range searchRange)
        {
            Range nullRange = new Range(Range.NotFound, 0);

            if (subject == null)
            {
                throw new NullReferenceException();
            }

            if (subject == string.Empty)
            {
                return Range.NullRange;
            }

            if (aString == null)
            {
                return Range.NullRange;
            }

            if (aString == string.Empty)
            {
                return Range.NullRange;
            }

            int location = subject.ToLower().IndexOf(aString.ToLower(), searchRange.Location, searchRange.Length);

            if (location == -1)
            {
                return Range.NullRange;
            }

            return new Range(location, aString.Length);
        }

        /// <summary>
        /// Returns a string object containing the characters of the receiver that lie within a given range.
        /// </summary>
        /// <param name="subject">string to work on</param>
        /// <param name="aRange">A range. The range must not exceed the bounds of the receiver.</param>
        /// <returns>A string object containing the characters of the receiver that lie within aRange.</returns>
        /// 
        public string SubstringWithRange(string subject, Range aRange)
        {
            return subject.Substring(aRange.Location, aRange.Length);
        }

        #region IRankProvider Members


        public void RegisterHit(ISearchable candidate)
        {
            return;
        }

        #endregion
    }
}
